
package jing.typedef.property;

import android.os.Parcel;
import android.os.Parcelable;

public class DataType implements Parcelable {

    private Type type = Type.INTEGER;
    private String strType = null;
    private Class<?> javaDataType = null;
    
    public enum Type {
        UNDEFINED,
        INTEGER,
        LONG,
        FLOAT,
        DOUBLE,
        STRING,
        BOOLEAN,
        BYTES;
       
       /**
        *  Integer              ui1, ui2, i1, i2, i4, int
        *  Long                 ui4, time, time.tz
        *  Float                r4, float
        *  Double               r8, number, fixed.14.4
        *  Character            char
        *  String               string, uri, uuid
        *  Date                 date, dateTime, dateTime.tz
        *  Boolean              boolean
        *  byte[]               bin.base64, bin.hex
        */
        private static final String STR_undefined = "undefined";
        private static final String STR_integer = "integer";
        private static final String STR_long = "long";
        private static final String STR_float = "float";
        private static final String STR_double = "double";
        private static final String STR_string = "string";
        private static final String STR_boolean = "boolean";
        private static final String STR_bytes = "bytes";

        public static Type retrieveType(String value) {
            if (value.equals(STR_undefined)) {
                return UNDEFINED;
            }

            if (value.equals(STR_integer)
                    || value.equals("ui1")
                    || value.equals("ui2") 
                    || value.equals("i1") 
                    || value.equals("i2") 
                    || value.equals("i4")
                    || value.equals("int")) {
                return INTEGER;
            }

            if (value.equals(STR_long)
                    || value.equals("ui4")
                    || value.equals("time") 
                    || value.equals("time.tz")) {
                return LONG;
            }

            if (value.equals(STR_float)
                    || value.equals("r4")) {
                return FLOAT;
            }

            if (value.equals(STR_double)
                    || value.equals("r8")
                    || value.equals("number") 
                    || value.equals("fixed.14.4")) {
                return DOUBLE;
            }

            if (value.equals(STR_string)
                    || value.equals("string")
                    || value.equals("uri") 
                    || value.equals("uuid"))  {
                return STRING;
            }

            if (value.equals(STR_boolean)) {
                return BOOLEAN;
            }
            
            if (value.equals(STR_bytes)
                    || value.equals("bin.base64")
                    || value.equals("bin.hex")) {
                return BYTES;
            }

            return UNDEFINED;
        }

        public static Type retrieveType(Class<?> t) {
            if (t.isAssignableFrom(Integer.class)) {
                return INTEGER; 
            }
            else if (t.isAssignableFrom(Long.class)) {
                return LONG;
            }
            else if (t.isAssignableFrom(Float.class)) {
                return FLOAT;
            }
            else if (t.isAssignableFrom(Double.class)) {
                return DOUBLE;
            }
            else if (t.isAssignableFrom(String.class)) {
                return STRING;
            }
            else if (t.isAssignableFrom(Boolean.class)) {
                return BOOLEAN;
            }
            else if (t.isAssignableFrom(byte[].class)) {
                return BYTES;
            }

            return UNDEFINED;
        }
        
        @Override
        public String toString() {
            String value;
            switch (this) {
                case BOOLEAN:
                    value = STR_boolean;
                    break;
                    
                case BYTES:
                    value = STR_bytes;
                    break;
                    
                case DOUBLE:
                    value = STR_double;
                    break;
                    
                case FLOAT:
                    value = STR_float;
                    break;
                    
                case INTEGER:
                    value = STR_integer;
                    break;
                    
                case LONG:
                    value = STR_long;
                    break;
                    
                case STRING:
                    value = STR_string;
                    break;

                default:
                    value = STR_undefined;
                    break;
            }

            return value;
        }
        
        public Class<?> toJavaDataType() {
            Class<?> t = null;
            
            switch (this) {
                case BOOLEAN:
                    t = Boolean.class;
                    break;
                    
                case BYTES:
                    t = byte[].class;
                    break;
                    
                case DOUBLE:
                    t = Double.class;
                    break;
                    
                case FLOAT:
                    t = Float.class;
                    break;
                    
                case INTEGER:
                    t = Integer.class;
                    break;
                    
                case LONG:
                    t = Long.class;
                    break;
                    
                case STRING:
                    t = String.class;
                    break;

                default:
                    break;
            }
    
            return t;
        }
    }

    public String getStringType() {
        return strType;
    }

    public Type getType() {
        return type;
    }

    public Class<?> getJavaDataType() {
        return javaDataType;
    }

    public static DataType create(Class<?> t) {
        DataType thiz = new DataType();
        if (! thiz.setType(t)) {
            return null;
        }

        return thiz;
    }

    public static DataType create(String t) {
        DataType thiz = new DataType();
        if (! thiz.setType(t)) {
            return null;
        }

        return thiz;
    }

    public static DataType create(Type t) {
        DataType thiz = new DataType();
        if (! thiz.setType(t)) {
            return null;
        }

        return thiz;
    }

    private boolean setType(Class<?> t) {
        Type tt = Type.retrieveType(t); 
        if ( tt == Type.UNDEFINED) {
            return false;
        }

        type = tt;
        javaDataType = t;
        strType = tt.toString();

        return true;
    } 

    private boolean setType(String t) {
        Type tt = Type.retrieveType(t); 
        if ( tt == Type.UNDEFINED) {
            return false;
        }

        type = tt;
        javaDataType = tt.toJavaDataType();
        strType = t;

        return true;
    } 

    private boolean setType(Type t) {
        if ( t == Type.UNDEFINED) {
            return false;
        }

        type = t;
        javaDataType = t.toJavaDataType();
        strType = t.toString();
        
        return true;
    }
    

    public Object valueOf(String value) {
        Object v = null;

        try {
            switch (type) {
                case BOOLEAN:
                    v = Boolean.valueOf(value);
                    break;
                    
                case BYTES:
                    v = value.getBytes();
                    break;
                    
                case DOUBLE:
                    v = Double.valueOf(value);
                    break;
                    
                case FLOAT:
                    v = Float.valueOf(value);
                    break;
                    
                case INTEGER:
                    v = Integer.valueOf(value);
                    break;
                    
                case LONG:
                    v = Long.valueOf(value);
                    break;
                    
                case STRING:
                    v = value;
                    break;
                    
                case UNDEFINED:
                    break;
                    
                default:
                    break;
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }

        return v;
    }
    
    public static final Creator<DataType> CREATOR = new Parcelable.Creator<DataType>() {

        @Override
        public DataType createFromParcel(Parcel in) {
            return new DataType(in);
        }

        @Override
        public DataType[] newArray(int size) {
            return new DataType[size];
        }
    };

    private DataType() {
        type = Type.UNDEFINED;
    }

    public DataType(Parcel in) {
        readFromParcel(in);
    }

    public void readFromParcel(Parcel in) {
        String t = in.readString();
        this.setType(t);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeString(strType);
    }
}